<script lang="ts">
import bignumber, { type BigNumber } from '@peng_kai/kit/libs/bignumber';

/**
  // 测试用例
  <div class="flex flex-wrap gap-4">
    <TNum :value="0" weakPad />
    <TNum :value="5000" weakPad />
    <TNum
      v-for="v of ['0', '5000', '5050.0', '555.000', '555.500', '550.000', '550.050', '555.0500', '0.00']" :key="v"
      :value="v" :decimals="8" format="pad-dec" weakPad
    />
  </div>
 */

export interface NumProps {
  /**
   * 数值
   */
  value: BigNumber.Value;
  /**
   * 小数位数
   */
  decimals?: number | [number, number];
  /**
   * 舍入方式
   *
   * - up: 向上取整
   * - down: 向下取整
   * - half-up: 四舍五入
   * - half-even: 银行家算法
   */
  round?: 'up' | 'down' | 'half-up' | 'half-even';
  /**
   * 格式化方式
   *
   * - original: 原始
   * - pad-dec: 自适应补零，即整数位多1位，小数位就少1位
   * - fixed-dec: 固定小数位数
   * - max-dec: 最大小数位数
   * - min-dec: 最小小数位数
   * - clamp-dec: 限制小数位数
   */
  format?: 'original' | 'pad-dec' | 'fixed-dec' | 'max-dec' | 'min-dec' | 'clamp-dec';
  /**
   * 着色
   *
   * - inherit: 继承
   * - neg: 负数
   * - pos: 正数
   * - full: 全部
   */
  colored?: 'inherit' | 'neg' | 'pos' | 'full';
  /**
   * 是否显示正数符号
   */
  showPos?: boolean;
  /**
   * 是否使用分组分隔符（千分符）
   */
  grouping?: boolean;
  /**
   * 是否弱化无价值填充
   */
  weakPad?: boolean;
}

// 匹配数字中无价值的填充(仅支持有小数的), 如 1.2000 => 1.2, 1.000 => 1
const insignRE = /\.?0+$/;

function formatPadDecimals(num: BigNumber.Instance, decimals: number, round: BigNumber.RoundingMode, grouping: boolean) {
  const decimalLen = num.decimalPlaces() ?? 0;
  const numLen = decimalLen ? num.toString().length - 1 : num.toString().length;
  const integerLen = numLen - decimalLen;
  let showDecimalLen = decimals;

  if (integerLen > 1) {
    showDecimalLen = decimals - integerLen + 1;
    showDecimalLen = showDecimalLen < 1 ? 1 : showDecimalLen;
  }

  return grouping ? num.toFormat(showDecimalLen, round) : num.toFixed(showDecimalLen, round);
}

function formatFixedDecimals(num: BigNumber.Instance, decimals: number, round: BigNumber.RoundingMode, grouping: boolean) {
  return grouping ? num.toFormat(decimals, round) : num.toFixed(decimals, round);
}

function formatMaxDecimals(num: BigNumber.Instance, decimals: number, round: BigNumber.RoundingMode, grouping: boolean) {
  const decimalLen = num.decimalPlaces() ?? 0;
  const showDecimalLen = Math.min(decimalLen, decimals);
  return grouping ? num.toFormat(showDecimalLen, round) : num.toFixed(showDecimalLen, round);
}

function formatMinDecimals(num: BigNumber.Instance, decimals: number, round: BigNumber.RoundingMode, grouping: boolean) {
  const decimalLen = num.decimalPlaces() ?? 0;
  const showDecimalLen = Math.max(decimalLen, decimals);
  return grouping ? num.toFormat(showDecimalLen, round) : num.toFixed(showDecimalLen, round);
}

function formatClampDecimals(num: BigNumber.Instance, decimals: [number, number], round: BigNumber.RoundingMode, grouping: boolean) {
  const decimalLen = num.decimalPlaces() ?? 0;
  const showDecimalLen = decimalLen < decimals[0] ? decimals[0] : decimalLen > decimals[1] ? decimals[1] : decimalLen;
  return grouping ? num.toFormat(showDecimalLen, round) : num.toFixed(showDecimalLen, round);
}

function formatOriginal(num: BigNumber.Instance, decimals: number, round: BigNumber.RoundingMode, grouping: boolean) {
  const decimalLen = num.decimalPlaces() ?? decimals;
  return grouping ? num.toFormat(decimalLen, round) : num.toString();
}
</script>

<script setup lang="ts">
const props = withDefaults(defineProps<NumProps>(), {
  value: () => bignumber(0),
  showPos: false,
  decimals: 2,
  colored: 'inherit',
  round: 'half-up',
  format: 'original',
  grouping: true,
  weakPad: false,
});

// const numSign = computed(() => {
//   const num = bignumber(props.value);
//   return num.lt(0) ? '-' : (num.gt(0) && props.showPos) ? '+' : '';
// });
// const numText = computed(() => {
//   const num = bignumber(props.value).abs();
//   const roundMode = ({
//     'up': bignumber.ROUND_UP,
//     'down': bignumber.ROUND_DOWN,
//     'half-up': bignumber.ROUND_HALF_UP,
//     'half-even': bignumber.ROUND_HALF_EVEN,
//   })[props.round];
//   const isRangeDecimals = ['clamp-dec'].includes(props.format);
//   let text = '';

//   if (isRangeDecimals) {
//     const decLen = Array.isArray(props.decimals)
//       ? props.decimals
//       : [props.decimals, props.decimals] as [number, number];
//     const format = ({
//       'clamp-dec': formatClampDecimals,
//     } as Record<string, typeof formatClampDecimals>)[props.format];

//     text = format(num, decLen, roundMode, props.grouping);
//   }
//   else {
//     const decLen = Array.isArray(props.decimals) ? props.decimals[0] : props.decimals;
//     const format = ({
//       'pad-dec': formatPadDecimals,
//       'fixed-dec': formatFixedDecimals,
//       'max-dec': formatMaxDecimals,
//       'min-dec': formatMinDecimals,
//     } as Record<string, typeof formatPadDecimals>)[props.format] ?? formatOriginal;

//     text = format(num, decLen, roundMode, props.grouping);
//   }

//   if (text) {
//     text = text.replace(/\.?0+$/, match => `<span class="${props.weakPad ? 'no-value' : ''}">${match}</span>`);
//     text = numSign.value + text;
//   }

//   return text;
// });
const numParts = computed(() => {
  const num = bignumber(props.value).abs();
  const roundMode = ({
    'up': bignumber.ROUND_UP,
    'down': bignumber.ROUND_DOWN,
    'half-up': bignumber.ROUND_HALF_UP,
    'half-even': bignumber.ROUND_HALF_EVEN,
  })[props.round];
  const isRangeDecimals = ['clamp-dec'].includes(props.format);
  const parts = { full: '', sign: '', value: '', pad: '' };
  let text = '';

  if (isRangeDecimals) {
    const decLen = Array.isArray(props.decimals)
      ? props.decimals
      : [props.decimals, props.decimals] as [number, number];
    const format = ({
      'clamp-dec': formatClampDecimals,
    } as Record<string, typeof formatClampDecimals>)[props.format];

    text = format(num, decLen, roundMode, props.grouping);
  }
  else {
    const decLen = Array.isArray(props.decimals) ? props.decimals[0] : props.decimals;
    const format = ({
      'pad-dec': formatPadDecimals,
      'fixed-dec': formatFixedDecimals,
      'max-dec': formatMaxDecimals,
      'min-dec': formatMinDecimals,
    } as Record<string, typeof formatPadDecimals>)[props.format] ?? formatOriginal;

    text = format(num, decLen, roundMode, props.grouping);
  }

  if (text) {
    const originalNum = bignumber(props.value);

    parts.sign = originalNum.lt(0) ? '-' : (originalNum.gt(0) && props.showPos) ? '+' : '';
    parts.full = parts.sign + text;
    parts.pad = text.match(insignRE)?.[0] ?? '';
    parts.pad = originalNum.decimalPlaces() || parts.pad[0] === '.' ? parts.pad : '';
    parts.value = text.slice(0, text.length - parts.pad.length);
  }

  return parts;
});
const classList = computed(() => {
  const num = bignumber(props.value);
  const classes = [`colored-${props.colored}`];

  if (num.lt(0))
    classes.push('sign-neg');
  else if (num.gt(0))
    classes.push('sign-pos');
  else
    classes.push('sign-zero');

  if (props.weakPad)
    classes.push('weak-pad');

  return classes;
});
</script>

<template>
  <span class="num" :class="classList">
    <slot name="signLeft" />{{ numParts.sign }}<slot name="signRight" /><slot name="numLeft" />{{ numParts.value }}<span class="pad">{{ numParts.pad }}</span><slot name="numRight" />
  </span>
</template>

<style lang="scss" scoped>
.num {
  font-variant-numeric: tabular-nums;

  &.sign-neg.colored-neg,
  &.sign-neg.colored-full {
    color: var(--neg-color, #ef4444);
  }

  &.sign-pos.colored-pos,
  &.sign-pos.colored-full {
    color: var(--pos-color, #22c55e);
  }

  &.weak-pad .pad {
    opacity: 0.5;
  }
}
</style>
